package cn.ibizlab.odoo.util.log;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

import static cn.ibizlab.odoo.util.log.LogMessage.*;

/**
 * 系统全局日志切面类
 * 对标记为@IBIZLog的类、方法生成系统日志。
 * ps：不支持类内部互相调用场景。
 *
 * @author nancy
 * @date 2020-3-20
 */
@Aspect
@Component
public class LogAspect {

    //切入标记为@IBIZLog的类及非私有方法，适用于类、方法。
    @Around("@annotation(cn.ibizlab.odoo.util.log.IBIZLog) || @within(cn.ibizlab.odoo.util.log.IBIZLog)")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //设置日志位置，aop类-->切入点位置
        String className = joinPoint.getSignature().getDeclaringTypeName();
        Logger log = LoggerFactory.getLogger(className);

        String methodSignature = getMethodSignature(joinPoint);

        MDC.put("method", methodSignature);
        log.info(FUNCTION_START.getMsg());
        log.debug(PARAMS.getMsg(), getStr(joinPoint.getArgs()));
        MDC.clear();
        //0代表正常结束，1抛出了异常，2准正常结束
        int status = 0;

        //监控返回值、参数、执行时间
        Object returnedValue = null;
        long start = System.currentTimeMillis();
        String errormsg = null;

        try {
            returnedValue = joinPoint.proceed();
            return returnedValue;
        } catch (Throwable throwable) {
            errormsg = throwable.getMessage();
            if (throwable instanceof RuntimeException) {
                status = 1;
                throwable.printStackTrace();
            } else {
                status = 2;
            }
            throw throwable;

        } finally {
            MDC.put("method", methodSignature);
            long timeCost = System.currentTimeMillis() - start;
            boolean isVoidReturnType = getMethodReturnType(joinPoint)==void.class;

            switch (status) {
                case 0:
                    //INFO日志：方法结束、耗时、方法返回值信息（简单信息）
                    log.info(FUNCTION_END.getMsg()+TIME_COST.msg+(isVoidReturnType?"":RETURNED_INFO.getMsg()),timeCost,(isVoidReturnType?"":getReturnInfo(joinPoint,returnedValue)));
                    if(!isVoidReturnType){
                        //DEBUG日志:方法返回值内容（Json格式）。
                        log.debug(RETURNED_VALUE.getMsg(),getStr(returnedValue));
                    }
                    break;
                case 1:
                    //异常结束打印信息。
                    log.error(FUCNTION_ERROR_END.getMsg(), methodSignature,errormsg);
                    break;
                case 2:
                    //抛出检查异常打印信息。
                    log.info(FUNCTION_EXCEPTION_END.getMsg()+TIME_COST.msg,timeCost);
                    log.debug(EXCEPTION_MSG.getMsg()+","+TIME_COST.getMsg(), errormsg, timeCost);
                    break;
                default:

            }
            MDC.clear();
        }
    }

   /**
     * 返回方法签名。方法名（参数类型1，参数类型2，...）
     * @param joinPoint 切入点
     * @return 方法签名（String）
     */
    private String getMethodSignature(ProceedingJoinPoint joinPoint) {
        String methodSignature = joinPoint.getSignature().toShortString();
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        Signature s = joinPoint.getSignature();
        MethodSignature ms = (MethodSignature)s;
        Method m = ms.getMethod();
        Class[] types = m.getParameterTypes();
        String[] paramtypes = new String[types.length];
        for(int i=0;i<types.length;i++){
            paramtypes[i] = types[i].getSimpleName();
        }
        return methodSignature.replace(className+".","").replace("..", String.join(",", paramtypes));
    }

    /**
     * 返回返回值信息。用于info级打印。
     * @param joinPoint 切入点
     * @param returnedValue   返回值（Object)
     * @return  返回值信息（String)
     */
    public static String getReturnInfo(ProceedingJoinPoint joinPoint,Object returnedValue){
        Class<?> returnType = getMethodReturnType(joinPoint);

        if(returnedValue==null){
            return String.format("null (%s)",returnType.getSimpleName());
        }
        if(isBaseType(returnType)){
            return String.valueOf(returnedValue);
        }
        return returnedValue.toString();
    }

    /**
     * 方法返回值类型
     * @param joinPoint 切入点
     * @return  返回值类型class
     */
    public static Class<?> getMethodReturnType(ProceedingJoinPoint joinPoint){
        Signature s = joinPoint.getSignature();
        joinPoint.getSignature().getDeclaringTypeName();
        MethodSignature ms = (MethodSignature) s;
        Method m = ms.getMethod();
        return m.getReturnType();
    }

    /**
     * 判断否为基本类型
     * @param className class对象
     * @return
     */
    private static boolean isBaseType(Class<?> className) {
        if (className.equals(java.lang.Integer.class) ||
                className.equals(java.lang.Byte.class) ||
                className.equals(java.lang.Long.class) ||
                className.equals(java.lang.Double.class) ||
                className.equals(java.lang.Float.class) ||
                className.equals(java.lang.Character.class) ||
                className.equals(java.lang.Short.class) ||
                className.equals(java.lang.Boolean.class)) {
            return true;
        }
        return false;
    }
}
